// Copyright (C)2012 Johann Duscher. All rights reserved.
//
// This file is part of QwTAPI.
//
// QwTAPI is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// QwTAPI is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with QwTAPI.  If not, see <http://www.gnu.org/licenses/>.

#include "Line.h"

#include "Connection.h"
#include "Call.h"
#include "Address.h"
#include "Request.h"


namespace qwtapi
{

Line::Line(Connection *pParent)
    : super(pParent),
      m_hLine(NULL),
      m_deviceId(NULL),
      m_apiVersion(NULL),
      m_pLineCaps(NULL),
      m_pLineStatus(NULL),
      m_callListMutex(QMutex::Recursive),
      m_addressListMutex(QMutex::Recursive)
{
}

Line::~Line()
{
    close();

    QMutexLocker alLock(&m_addressListMutex);
    foreach (Address* pAddress, m_addressList)
        delete pAddress;
    m_addressList.clear();

    QMutexLocker clLock(&m_callListMutex);
    foreach (Call* pCall, m_callList)
        delete pCall;
    m_callList.clear();

    if (m_pLineCaps)
        free(m_pLineCaps);

    if (m_pLineStatus)
        free(m_pLineStatus);
}

Connection* Line::getConnection() const
{
    return static_cast<Connection*>(parent());
}

Address* Line::getAddressFromId(DWORD addressId) const
{
    QMutexLocker lock(&m_addressListMutex);
    
    if (addressId >= m_addressList.size())
        return NULL;

    return m_addressList[addressId];
}

QString Line::getServiceProviderInfo() const
{
    QString providerInfo = tr("Provider unknown");

    if (!m_pLineCaps)
        return providerInfo;

    if (!m_pLineCaps->dwProviderInfoSize || !m_pLineCaps->dwProviderInfoOffset)
        return providerInfo;

    switch (m_pLineCaps->dwStringFormat)
    {
    case StringFormat::ASCII:
        {
            LPCSTR pProviderInfo = reinterpret_cast<LPCSTR>(m_pLineCaps) + m_pLineCaps->dwProviderInfoOffset;
            return QString(pProviderInfo);
        }
    case StringFormat::UNIC:
        {
            LPCWSTR pProviderInfo = reinterpret_cast<LPCWSTR>(m_pLineCaps) + m_pLineCaps->dwProviderInfoOffset;
            return QString::fromStdWString(pProviderInfo);
        }
    default:
        break;
    }

    return tr("Line name encoding not supported");
}

LONG Line::initialize(DWORD deviceId)
{
    // Negotiate the API version with TAPI.
    LINEEXTENSIONID lineExtID = {};
    LONG errCode = lineNegotiateAPIVersion (
        getConnection()->getLineAppHandle(),
        deviceId,
        TAPI_VERSION_1_3, TAPI_VERSION_2_0, &m_apiVersion,
        &lineExtID);

    if (0 != errCode)
        return errCode;

    // Remember the device id.
    m_deviceId = deviceId;

    // Gather all the address information for each address on the line
    gatherAddressInformation();

    return errCode;
}

void Line::gatherAddressInformation()
{
    if (NULL == getCapabilities())
        return;

    QMutexLocker lock(&m_addressListMutex);
    for (DWORD i = 0; i < getAddressCount(); i++)
    {
        Address* pAddress = new Address(this); // TODO: Use factory mechanism.
        pAddress->initialize(i);
        m_addressList += pAddress;
    }    
}

DWORD Line::getAddressCount() const
{
    LPLINEDEVCAPS pCaps = getCapabilities();
    if (pCaps)
        return pCaps->dwNumAddresses;

    return 0;
}

HLINE Line::getLineHandle() const
{
    return m_hLine;
}

DWORD Line::getDeviceId() const
{
    return m_deviceId;
}

DWORD Line::getNegotiatedAPIVersion() const
{
    return m_apiVersion;
}

LONG Line::open(CallPrivilege::Constants callPrivileges, MediaMode::Constants mediaModes, DWORD apiVersion, DWORD extVersion, LPLINECALLPARAMS const pCallParams)
{
    if (isOpen())
        return FALSE;

    // If no version information given, use the highest negotiated during
    // our INIT process.
    if (apiVersion == 0)
        apiVersion = getNegotiatedAPIVersion();

    LONG errCode = lineOpen (getConnection()->getLineAppHandle(), getDeviceId(),
        &m_hLine,
        apiVersion, extVersion,
        (DWORD)this, callPrivileges, mediaModes, pCallParams);

    if (0 != errCode)
        m_hLine = NULL;

    return errCode;
}

bool Line::isOpen() const
{
    return NULL != m_hLine;
}

LONG Line::close()
{
    if (!isOpen())
        return FALSE;

    LONG errCode = lineClose(getLineHandle());
    if (0 == errCode)
        onClose();

    return errCode;
}

QString Line::getName() const
{
    const LPLINEDEVCAPS pLineCaps = getCapabilities();
    if (!pLineCaps)
        return tr("Line unavailable");

    if (!pLineCaps->dwLineNameSize || !pLineCaps->dwLineNameOffset)
        return tr("Line unnamed");

    switch (pLineCaps->dwStringFormat)
    {
    case StringFormat::ASCII:
        {
            LPCSTR pLineName = reinterpret_cast<LPCSTR>(pLineCaps) + pLineCaps->dwLineNameOffset;
            return QString(pLineName);
        }
    case StringFormat::UNIC:
        {
            LPCWSTR pLineName = reinterpret_cast<LPCWSTR>(pLineCaps) + pLineCaps->dwLineNameOffset;
            return QString::fromStdWString(pLineName);
        }
    default:
        break;
    }

    return tr("Line name encoding not supported");
}

const LPLINEDEVCAPS Line::getCapabilities() const
{
    if (!m_pLineCaps)
        const_cast<Line*>(this)->refreshCapabilities();

    return m_pLineCaps;
}

LONG Line::refreshCapabilities(DWORD apiVersion, DWORD extVersion)
{
    // If there is no version information, then use our negotiated version.
    if (apiVersion == 0)
        apiVersion = getNegotiatedAPIVersion();

    // Allocate a buffer for the line capabilities
    DWORD dwSize = (m_pLineCaps) ? m_pLineCaps->dwTotalSize : sizeof(LINEDEVCAPS)+1024;
    for (int i = 0; i < 10; i++)
    {
        if (!m_pLineCaps)
        {
            m_pLineCaps = static_cast<LPLINEDEVCAPS>(calloc(1, dwSize));
            if (!m_pLineCaps)
                return LINEERR_NOMEM;
        }

        // Mark the size we are sending.
        ((LPVARSTRING)m_pLineCaps)->dwTotalSize = dwSize;
        LONG errCode = lineGetDevCaps(getConnection()->getLineAppHandle(), getDeviceId(), apiVersion, extVersion, m_pLineCaps);
        if (0 != errCode)
            return errCode;

        // If we allocated enough memory we clear the part not being used and return without error.
        if (m_pLineCaps->dwNeededSize <= dwSize)
        {
            memset(reinterpret_cast<LPBYTE>(m_pLineCaps) + m_pLineCaps->dwUsedSize, 0, dwSize - m_pLineCaps->dwUsedSize);
            return 0;
        }

        // The memory we allocated was too small, so we increase the size and retry.
        dwSize = m_pLineCaps->dwNeededSize;
        free(m_pLineCaps);
        m_pLineCaps = NULL;
    }
    return LINEERR_NOMEM;
}

Call* Line::allocateCall(HCALL hCall)
{
    if (NULL == hCall)
        return NULL;

    // Lock the array up front so no other thread can
    // come through here while we are searching/creating
    // the call.  Otherwise, timing conditions could cause
    // TWO objects to be created for the same call.
    QMutexLocker lock(&m_callListMutex);

    // If we already have a call for the given handle return it.
    Call* pCall = findCallForHandle(hCall);
    if (pCall)
        return pCall;

    // We don't han one, so we create a new call object.
    pCall = new Call(this, hCall); // TODO: Use factory mechanism.
    pCall->initialize();
    m_callList += pCall;

    return pCall;
}

Call* Line::findCallForHandle(HCALL hCall) const
{
    foreach (Call* pCall, m_callList)
        if (pCall->getCallHandle() == hCall)
            return pCall;

    return NULL;
}

bool Line::deallocateCall(Call* pCall)
{
    QMutexLocker lock(&m_callListMutex);
    if (m_callList.removeOne(pCall))
    {
        if (0 == pCall->deallocate())
        {
            pCall->deleteLater();
            return true;
        }
    }

    return false;
}

Line::CallList Line::getCalls() const
{
    return m_callList;
}

LONG Line::makeCall(const QString& destAddress, DWORD country, LPLINECALLPARAMS const pCallParams)
{
    QScopedPointer<Request> spRequest(getConnection()->createRequest(this, SLOT(onMakeCallResult(const Request*))));
    LONG result = lineMakeCall(getLineHandle(), &spRequest->callHandle(), destAddress.toStdWString().c_str(), country, pCallParams);
    if (result >= 0)
        getConnection()->addRequest(spRequest.take(), lineMakeCall, result);

    return result;
}

void Line::onMakeCallResult(const Request* pRequest)
{
    Q_ASSERT(lineMakeCall == pRequest->getRequestType());
    
    if (0 == pRequest->getResult())
        allocateCall(pRequest->callHandle());
}

void Line::onClose()
{
    m_hLine = NULL;

    QMutexLocker lock(&m_callListMutex);
    
    foreach (Call* pCall, CallList(m_callList))
    {
        pCall->drop();
        deallocateCall(pCall);
    }
    
    m_callList.clear();
}

void Line::onForceClose()
{
    onClose();
}

void Line::onLineEvent(DWORD hDevice, Message::Constant msg, DWORD dwParam1, DWORD dwParam2, DWORD dwParam3)
{
    super::onLineEvent(hDevice, msg, dwParam1, dwParam2, dwParam3);

    emit tapiLineEvent(hDevice, msg, dwParam1, dwParam2, dwParam3);
}

void Line::onAppNewCall(DWORD addressId, HCALL hCall, CallPrivilege::Constant privilege)
{
    allocateCall(hCall);

    super::onAppNewCall(addressId, hCall, privilege);
}

void Line::onCallState(HCALL hCall, CallState::Constant callState, DWORD callStateDetail, CallPrivilege::Constant callPrivilege)
{
    // Find the call - create a new call if one doesn't exist.
    Call* pCall = allocateCall(hCall);
    if (pCall)
    {
        switch (callState)
        {
        case CallState::DISCONNECTED:
            pCall->drop();
            break;
        case CallState::IDLE:
            deallocateCall(pCall);
        default:
            break;
        }
    }

    super::onCallState(hCall, callState, callStateDetail, callPrivilege);
}

}
